import binascii
import os
# following imports are required by PKI
import Crypto
import Crypto.Random
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5  # 用于签名
from Crypto.Hash import SHA  # 用于签名
import hashlib  # 用于hash计算
import configparser  # 处理ini文件
import time
import datetime
import base64

#iniFileName = "Account.ini" #用于存储账户余额
txtFileName = 'TranscationLists.txt'  # 用于存储交易信息
#TransactionList = [] #用来存储所有的交易信息
#将交易信息存储到文件中
blockRootPath = os.path.abspath('.')  # current dir
blockDir = blockRootPath + '/block'  # block file directory


def findBlocFiles(filesPath):  # 查找指定文件夹下的区块链文件
    #查找文件代码
    if not os.path.exists(filesPath):
        os.makedirs(filesPath)
    files = os.listdir(filesPath)
    filesList = []
    for s in files:
        sPath = os.path.join(filesPath, s)
        if os.path.isdir(sPath):
            findBlocFiles(sPath, filesList)
        elif os.path.isfile(sPath) and 'block' in s:
            filesList.append(sPath)
    return filesList


def findTranFiles(filesPath):  # 查找指定文件夹下的交易
    #查找文件代码
    if not os.path.exists(filesPath):
        os.makedirs(filesPath)
    files = os.listdir(filesPath)
    filesList = []
    for s in files:
        sPath = os.path.join(filesPath, s)
        if os.path.isdir(sPath):
            findTranFiles(sPath, filesList)
        elif os.path.isfile(sPath) and 'tran' in s:
            filesList.append(sPath)
    return filesList


def GetLastBlockInfo():  # 找到最后一个区块的ID和Hash值
    fList = findBlocFiles(blockDir)
    fCount = len(fList)
    if fCount == 0:
        #生产0号/创世区块
        blockId = 0
        previousBlockHash = 0
        blockTranStr = '-'
        timestamp = int(time.time())
        miner = '-'
        blockStr1 = "blockId:%s,previousBlockHash:%s,blockTranStr:%s,miner:%s,timestamp:%s" % (
            blockId, previousBlockHash, blockTranStr, miner, timestamp)
        blockHash = Crypto.Hash.SHA256.new()
        blockHash.update(blockStr1.encode('utf-8'))
        blockHashStr = blockHash.hexdigest()
        blockStr2 = "%s,blockHash:%s\n\n\n" % (blockStr1, blockHashStr)
        newBlockFile = r"%s\block%s.txt" % (blockDir, blockId)
        f = open(newBlockFile, "a+")
        f.write(blockStr2)
        f.close()
    else:
        blockId = fCount - 1
        blockFile = r"%s\block%s.txt" % (blockDir, blockId)
        blockHashStr = ''
        f = open(blockFile, "r")
        strs = f.read()
        f.close()
        strs = strs.split('\n\n\n')
        for str in strs:
            list1 = str.split(',')
            for lStr in list1:
                if lStr.split(":")[0] == 'blockHash':
                    blockHashStr = lStr.split(":")[1]

    return blockId, blockHashStr


def VerifyBlock(blockFileName):
    #从交易文件中导入交易信息
    blockList = []
    f = open(blockFileName, "r")
    str1 = f.read()
    f.close()

    blockList.append(str1)
    #验证TransactionList中的交易记录，如果正确则保存到交易记录账本中
    rstValue = False
    blockTranStr = ''  # block的交易字符串
    for blocStr in blockList:
        if len(blocStr) > 0:
            #block format
            # blockId:%s,previousBlockHash:%s,blockTranStr:[%s],miner:%s,timestamp:%s,blockHash:%s
            list1 = blocStr.split(',')
            blockId = list1[0].split(":")[1]
            previousBlockHash = list1[1].split(":")[1]
            blockTranStr = list1[2]
            miner = list1[3].split(":")[1]
            timestamp = list1[4].split(":")[1]
            blockHash = list1[5].split(":")[1]

            str1 = "blockId:%s,previousBlockHash:%s,blockTranStr:[%s],miner:%s,timestamp:%s" % (
                blockId, previousBlockHash, blockTranStr, miner, timestamp)
            msg_hash = Crypto.Hash.SHA256.new()
            msg_hash.update(str1.encode('utf-8'))
            hStr1 = msg_hash.hexdigest()
            if blockHash == blockHash:
                rstValue = True
            #只验证了hash，还没有验证每一笔交易
    return rstValue


def GetAccountCreditOld(address=''):
    f = open(txtFileName, "r")
    tranStr = f.read()
    f.close()
    strs = tranStr.split('\n\n')
    rstValue = 0
    for str in strs:
        if len(str) > 0:
            list1 = str.split(',')
            sender = list1[0].split(":")[1]
            recipient = list1[1].split(":")[1]
            valueStr = list1[2].split(":")[1]
            timestampStr = list1[3].split(":")[1]
            signStr = list1[4].split(":")[1]
            pubkey = list1[5].split(":")[1]
            if address == recipient:
                rstValue += float(valueStr)
    print("账户:%s 的余额为:%.8f" % (address, rstValue))
    return rstValue


def GetAccountCredit(address=''):
    rstValue = 0  # 余额
    fList = findBlocFiles(blockDir)
    fCount = len(fList)
    #计算每一个区块中的账户信息
    for i in range(1, fCount, 1):
        blocFilename = blockDir + "/block%d.txt" % i
        vResult = VerifyBlock(blocFilename)
        if vResult:
            blockList = []
            f = open(blocFilename, "r")
            str1 = f.read()
            f.close()
            blockList.append(str1)
            for blocStr in blockList:
                if len(blocStr) > 0:
                    #block format
                    # blockId:%s,previousBlockHash:%s,blockTranStr:[%s],miner:%s,timestamp:%s,blockHash:%s
                    list1 = blocStr.split(',')
                    blockId = list1[0].split(":")[1]
                    previousBlockHash = list1[1].split(":")[1]
                    blockTranStr = list1[2]
                    miner = list1[3].split(":")[1]
                    timestamp = list1[4].split(":")[1]
                    blockHash = list1[5].split(":")[1]

                    blockTranStr = blockTranStr.split(":", 1)[1]
                    blockTranStr = blockTranStr.replace(
                        "[", "").replace("]", "")
                    list2 = blockTranStr.split(';')
                    sender = list2[0].split(":")[1]
                    recipient = list2[1].split(":")[1]
                    valueStr = list2[2].split(":")[1]
                    timestampStr = list2[3].split(":")[1]
                    signStr = list2[4].split(":")[1]
                    pubkey = list2[5].split(":")[1]

                    if address == sender:
                        rstValue -= float(valueStr)  # 挖矿的奖励
                    if address == recipient:
                        rstValue += float(valueStr)  # 挖矿的奖励
                    if address == miner:
                        rstValue += 1  # 挖矿的奖励

    print("账户:%s 的余额为:%.8f" % (address, rstValue))
    return rstValue


class Student:
    Name = ''
    address = ''
    Credits = 20.0
    private_key = ''  # 修改为可以外部访问
    public_key = ''  # 修改为可以外部访问

    def __init__(self, stuName):
        PrivateFile = "%s_Private.pem" % (stuName)
        if os.path.exists(PrivateFile):
            #导入已有账户的私钥
            with open(PrivateFile, "rb") as f:
                privatePemStr = f.read()
                self.private_key = Crypto.PublicKey.RSA.importKey(
                    privatePemStr)
        else:
            #创建新的账户
            random = Crypto.Random.new().read
            self.private_key = RSA.generate(1024, random)
            # 保存密钥
            with open(PrivateFile, "wb") as f:
                privatePemStr = self.private_key.exportKey()   # 导出私钥
                f.write(privatePemStr)
                f.close()

        publickFile = "%s_Public.pem" % (stuName)
        if os.path.exists(publickFile):
            #导入已有账户的公钥
            with open(publickFile, "rb") as f:
                publicPemStr = f.read()
                self.public_key = Crypto.PublicKey.RSA.importKey(publicPemStr)
        else:
            self.public_key = self.private_key.publickey()
            publicPemStr = self.public_key.exportKey()   # 导出公钥
            with open(publickFile, "wb") as f:
                f.write(publicPemStr)
        #计算出账户
        str1 = self.public_key.exportKey(format='PEM').decode('utf-8')
        h1 = Crypto.Hash.SHA256.new(str1.encode('utf-8'))
        str2 = binascii.hexlify(h1.digest()).decode('utf-8')
        h2 = Crypto.Hash.SHA256.new(str2.encode('utf-8'))
        str3 = binascii.hexlify(h2.digest()).decode('utf-8')
        self.address = str3.upper()
        print("Account is ready: %s" % (self.address))


class Miner(Student):  # 继承
    #矿工，负责验证交易，并把验证通过的交易记录到账本中
    def VerifyTransaction(self, tranFilename):
        #从交易文件中导入交易信息
        TransactionList = []
        f = open(tranFilename, "r")
        str1 = f.read()
        f.close()
        os.remove(tranFilename)

        TransactionList.append(str1)
        #验证TransactionList中的交易记录，如果正确则保存到交易记录账本中
        rstValue = False
        blockTranStr = ''  # block的交易字符串
        for str in TransactionList:
            if len(str) > 0:
                rstValue = False
                list1 = str.split(';')
                sender = list1[0].split(":")[1]
                recipient = list1[1].split(":")[1]
                valueStr = list1[2].split(":")[1]
                timestampStr = list1[3].split(":")[1]
                signStr = list1[4].split(":")[1]
                pubkey = list1[5].split(":")[1]
                tranDetailStr = "sender:%s;recipient:%s;value:%s;timestamp:%s" % (
                    sender, recipient, valueStr, timestampStr)
                print(str)

                pubkeyPem = base64.b32decode(pubkey)
                keyPub = Crypto.PublicKey.RSA.importKey(pubkeyPem)
                verifer = Crypto.Signature.PKCS1_v1_5.new(keyPub)
                msg_hash = Crypto.Hash.SHA256.new()
                msg_hash.update(tranDetailStr.encode('utf-8'))
                hStr1 = msg_hash.hexdigest()
                rstValue = verifer.verify(
                    msg_hash, base64.b32decode(signStr))  # 使用公钥验证签名

                if rstValue:
                    str1 = str + "\n"
                    blockTranStr += str1
        TransactionList = []  # clear transction list

        if len(blockTranStr) > 0:
            previousBlockId, previousBlockHash = GetLastBlockInfo()
            blockId = int(previousBlockId) + 1
            timestamp = int(time.time())  # 时间戳
            miner = self.address
            formatBlockDetail = "blockId:%s,previousBlockHash:%s,blockTranStr:[%s],miner:%s,timestamp:%s"
            blockStr1 = formatBlockDetail % (
                blockId, previousBlockHash, blockTranStr, miner, timestamp)
            blockHash = Crypto.Hash.SHA256.new()
            blockHash.update(blockStr1.encode('utf-8'))
            blockHashStr = blockHash.hexdigest()
            blockStr2 = "%s,blockHash:%s" % (blockStr1, blockHashStr)
            newBlockFile = r"%s\block%s.txt" % (blockDir, blockId)
            f = open(newBlockFile, "w+")
            f.write(blockStr2)
            f.close()

        return


class Transaction:
    def __init__(self, stu):
        self._private_key = stu.private_key
        self._private_key_str = base64.b32encode(
            self._private_key.exportKey(format='PEM'))
        self._public_key = stu.public_key
        self._public_key_str = base64.b32encode(
            self._public_key.exportKey(format='PEM'))
        return

    def Transfer(self, sender, recipient, value):
        self.timestamp = int(time.time())
        print('Transaction finished at %s' % (self.timestamp))
        formatTranDetail = "sender:%s;recipient:%s;value:%.8f;timestamp:%s"
        tranDetailStr = formatTranDetail % (
            sender.address, recipient.address, value, self.timestamp)
        signStr = self.SignTransaction(tranDetailStr)
        str1 = "%s;signstr:%s;pubkey:%s" % (
            tranDetailStr, signStr, self._public_key_str.decode('utf-8'))

        trnHash = Crypto.Hash.SHA256.new()
        trnHash.update(str1.encode('utf-8'))
        tranHashStr = trnHash.hexdigest()

        str2 = str1 + ";tranHash:%s" % tranHashStr
        #TransactionList.append(str1)
        tranFilename = blockDir + \
            '/tran_%s_%s.txt' % (self.timestamp, tranHashStr)
        self.TransactionSave(tranFilename, str2)
        return

    def TransactionSave(self, tranFilename, tranStr):  # 存储交易信息
        #输入文件名，和交易字符串
        f = open(tranFilename, "a+")
        f.write(tranStr)
        f.close()
        return

    def SignTransaction(self, tranStr=''):  # 返回签名后的字符串, tranStr为源字符串、没有经过hash处理
        signer = PKCS1_v1_5.new(self._private_key)
        h = Crypto.Hash.SHA256.new(tranStr.encode('utf-8'))
        hStr = h.hexdigest()
        str2 = signer.sign(h)
        str3 = base64.b32encode(str2).decode('utf-8')
        return str3


StuA = Student('A')
StuB = Student('B')
tran = Transaction(StuA)    # StuA发起的交易
tran.Transfer(StuA, StuB, 1.1)
#tran.Transfer(StuA,StuB,1.2)
minerA = Miner('minerA')

fList = findTranFiles(blockDir)
for f in fList:
    minerA.VerifyTransaction(f)

GetAccountCredit(StuA.address)
GetAccountCredit(StuB.address)
GetAccountCredit(minerA.address)
